<?php

namespace common\service;

use common\exception\LogicException;
use common\model\EntityModel;
use common\model\MediaModel;
use common\model\StatusModel;
use common\model\StepWorkflowModel;
use common\model\TaskModel;
use common\model\UserModel;
use malkusch\lock\mutex\PHPRedisMutex;
use support\bootstrap\Redis;
use support\ErrorCode;
use support\SingletonTrait;
use think\Exception;
use Webman\Stomp\Client;

class SequenceService
{
    use SingletonTrait;

    /**
     * 合并分镜的属性到序列中
     * @param array $storyboards
     * @return array
     */
    public function mergeSequenceAttrByStoryboards(array $storyboards)
    {
        $resAttr = [
            'sequence_perform_form' => '文戏', // 表演类型
            'sequence_perform_numbers' => 0, //表演人数
            'sequence_perform_species' => '无', // 表演种族
            'sequence_production_method' => '捕捉', // 制作方式
            'sequence_duration' => '~3', // 序列时长
            'sequence_style' => '写实', // 序列风格
            'sequence_level' => '', // 序列关联关卡
            'sequence_asset' => '', // 序列关联资产
//            'sequence_cloth_solution' => '引擎解算', // 布料解算
            'sequence_is_cluster' => '',
        ];

        // perform_form
        $sequencePerformFormList = array_column($storyboards, 'perform_form');
        $sequencePerformFormList = array_unique($sequencePerformFormList);
        if (in_array('文戏、武戏', $sequencePerformFormList)) {
            $resAttr['sequence_perform_form'] = '文戏、武戏';
        } else if (in_array('武戏', $sequencePerformFormList)) {
            $resAttr['sequence_perform_form'] = '武戏';
        } else {
            $resAttr['sequence_perform_form'] = '文戏';
        }

        //  sequence_perform_numbers
        $relateAssetsList = array_column($storyboards, 'asset');
        $relateAssets = [];
        foreach ($relateAssetsList as $relateStr) {
            if (!empty($relateStr)) {
                $relateAssets = array_merge($relateAssets, explode(',', $relateStr));
            }
        }
        $relateAssets = array_unique($relateAssets);

        $resAttr['sequence_asset'] = implode(',', $relateAssets);
        $sequencePerformNumbers = 0;
        if (count($relateAssets) > 0) {
            $relateAssets = model(EntityModel::class)->where(['id' => ['IN', $relateAssets]])
                ->field('id,json_unquote(json_extract(json,\'$.asset_category\')) as category')
                ->select();
            $relateAssets = array_filter($relateAssets, function ($item) {
                return $item['category'] === '角色';
            });

            $sequencePerformNumbers = count($relateAssets);
        }
        if ($sequencePerformNumbers <= 0) {
            $sequencePerformNumbers = 0;
        } else if ($sequencePerformNumbers > 5) {
            $sequencePerformNumbers = '~5';
        }
        $resAttr['sequence_perform_numbers'] = $sequencePerformNumbers;

        // sequence_perform_species

        $sequencePerformSpeciesList = array_column($storyboards, 'perform_species');
        $sequencePerformSpeciesPieces = [];
        array_map(function ($item) use (&$sequencePerformSpeciesPieces) {
            if (false !== strstr($item, '、')) {
                $sequencePerformSpeciesPieces = array_merge($sequencePerformSpeciesPieces, explode('、', $item));
            } else {
                $sequencePerformSpeciesPieces[] = $item;
            }
        }, $sequencePerformSpeciesList);
        $sequencePerformSpeciesPieces = array_unique($sequencePerformSpeciesPieces);

        $attrPiecesCount = count($sequencePerformSpeciesPieces);
        if ($attrPiecesCount > 0) {

            if (in_array('人', $sequencePerformSpeciesPieces) && in_array('机甲', $sequencePerformSpeciesPieces) && in_array('生物', $sequencePerformSpeciesPieces)) {
                $resAttr['sequence_perform_species'] = '人、机甲、生物';
            } else if (in_array('人', $sequencePerformSpeciesPieces) && in_array('机甲', $sequencePerformSpeciesPieces)) {
                $resAttr['sequence_perform_species'] = '人、机甲';
            } else if (in_array('人', $sequencePerformSpeciesPieces) && in_array('生物', $sequencePerformSpeciesPieces)) {
                $resAttr['sequence_perform_species'] = '人、生物';
            } else if (in_array('机甲', $sequencePerformSpeciesPieces) && in_array('生物', $sequencePerformSpeciesPieces)) {
                $resAttr['sequence_perform_species'] = '机甲、生物';
            } else if (!in_array('无', $sequencePerformSpeciesPieces)) {
                // 单属性
                $resAttr['sequence_perform_species'] = $sequencePerformSpeciesPieces[0];
            } else {
                $resAttr['sequence_perform_species'] = '无';
            }
        }
        // sequence_production_method

        $sequenceProductionMethodList = array_column($storyboards, 'production_method');
//        $sequenceProductionMethodList = array_unique($sequenceProductionMethodList);
        if (in_array('手K', $sequenceProductionMethodList)) {
            $resAttr['sequence_production_method'] = '手K';
        } else {
            $resAttr['sequence_production_method'] = '捕捉';
        }
        // sequence_duration

        $totalDuration = 0;
        foreach ($storyboards as $shot) {
            if (preg_match('|(\d+)|', $shot['duration'], $r)) {
                $totalDuration += $r[1];
            }
        }
        $resAttr['sequence_duration'] = AttributeValueService::getInstance()->getDurationAttribute($totalDuration);

        // 镜头风格检测
        $styles = array_filter(array_column($storyboards, 'style'), function ($item) {
            return !empty($item);
        });

        $styles = array_unique($styles);
        if (count($styles) > 1) {
            throw_strack_exception("Lens style must be unified.", ErrorCode::LENS_STYLE_MUST_BE_UNIFIED);
        }
        if (count($styles) > 0) {
            $resAttr['sequence_style'] = array_pop($styles);
        }

        // sequence_level
        $relateLevelsList = array_column($storyboards, 'level');
        $relateLevels = [];
        foreach ($relateLevelsList as $relateStr) {
            if (!empty($relateStr)) {
                $relateLevels = array_merge(explode(',', $relateStr));
            }
        }
        $relateLevels = array_unique($relateLevels);

        $resAttr['sequence_level'] = implode(',', $relateLevels);

        return $resAttr;
    }

    /**
     * 组装动捕序列
     * @param array $storyboardId
     * @param array $sequenceData
     * @param int $tenantId
     * @return array|bool
     * @throws Exception
     * @throws \Throwable
     */
    public function assembleMotionSequence(array $storyboardId, array $sequenceData, int $tenantId)
    {
        /**
         * 1. 查询出要组装的分镜
         * 2. 合并动捕数据
         * 3. 添加动捕
         * 4. 更新分镜和动捕的关系
         */
        $fields = [
            "id",
            "name",
            "code",
            "tenant_id",
            "project_id",
            "duration",
            "description",
            "initial_episode_code",
            "showings_number",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_sequence')) AS sequence",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_text_script')) AS text_script",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_cloth_solution')) AS cloth_solution",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_fx')) AS fx",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_illumination')) AS illumination",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS asset",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_level')) AS level",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_style')) AS style",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_type')) AS type",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_species')) AS perform_species",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_numbers')) AS perform_numbers",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_form')) AS perform_form",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_production_method')) AS production_method",
            "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_motion_sequence')) AS motion_sequence"
        ];

        $storyboardList = model(EntityModel::class)->where(['id' => ['IN', $storyboardId]])->field($fields)->select();
        if (empty($storyboardList)) {
            throw new LogicException("missing storyboard", ErrorCode::MISSING_MATCH_ENTITY_DATA);
        }
        $firstStoryboard = $storyboardList[0];

        // 获取集数映射字典
        $episodeData = model(EntityModel::class)->field('id,name,code,module_id')
            ->where([
                'project_id' => $firstStoryboard['project_id'],
                'module_id' => module_by_code('episode')['id'],
                'code' => $firstStoryboard['initial_episode_code']
            ])
            ->find();
        if (empty($episodeData)) {
            throw new LogicException("missing Episode", ErrorCode::MISSING_EPISODE);
        }

        $sequenceAttr = $this->mergeSequenceAttrByStoryboards($storyboardList);
        $sequenceStoryboardMap = [];
        $needRemoveStoryboardSequenceList = [];
        foreach ($storyboardList as $storyboard) {
            if (intval($storyboard['motion_sequence']) > 0) {
                $needRemoveStoryboardSequenceList[$storyboard['motion_sequence']] = $storyboard['motion_sequence'];
                $sequenceStoryboardMap[$storyboard['motion_sequence']][] = $storyboard['id'];
            }
        }

        $sequenceList = [];
        if (count($needRemoveStoryboardSequenceList) > 0) {
            $sequenceList = model(EntityModel::class)->where(['id' => ['IN', implode(',', $needRemoveStoryboardSequenceList)]])->field("id,json->>'$.sequence_storyboard' AS sequence_storyboard")->select();
            foreach ($sequenceList as &$sequenceItem) {
                $sequenceItem['sequence_storyboard'] = explode(',', $sequenceItem['sequence_storyboard']);
                $sequenceItem['sequence_storyboard'] = array_diff($sequenceItem['sequence_storyboard'], $sequenceStoryboardMap[$sequenceItem['id']] ?? []);
                $sequenceItem['sequence_storyboard'] = implode(',', $sequenceItem['sequence_storyboard']);
            }
        }

        $statusNotStart = StatusService::getInstance()->getOne(['code' => 'not_started'], 'id,code');
        $taskReadyStatus = StatusService::getInstance()->getTaskDefaultReadyStatus();
        $storyboardIdStr = implode(',', array_column($storyboardList, 'id'));
        $stepWorkflowCode = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_sequence_step_workflow_code');
        if (empty($stepWorkflowCode)) {
            throw new LogicException('missing config key task_config[motion_sequence_step_workflow_code]', ErrorCode::MISSING_CONFIG);
        }
        $stepWorkflow = model(StepWorkflowModel::class)->where(['code' => $stepWorkflowCode])->field('id')->find();
        if (empty($stepWorkflow)) {
            throw new LogicException("missing step workflow $stepWorkflowCode", ErrorCode::STEP_WORKFLOW_CONFIG_NOT_FOUND);
        }
        // 序列数据
        $sequenceData = array_merge([
            'code' => generate_code($sequenceData['name'], ""),
            'tenant_id' => $firstStoryboard['tenant_id'],
            'project_id' => $firstStoryboard['project_id'],
            'module_id' => module_by_code('sequence')['id'],
            'step_workflow_id' => $stepWorkflow['id'],
            'initial_episode_code' => $firstStoryboard['initial_episode_code'],
            'entity_id' => $episodeData['id'],
            'entity_module_id' => $episodeData['module_id'],
            'showings_number' => $firstStoryboard['showings_number'],
            'json' => [
                'sequence_style' => $sequenceAttr['sequence_style'],
                'sequence_perform_species' => $sequenceAttr['sequence_perform_species'],
                'sequence_perform_numbers' => $sequenceAttr['sequence_perform_numbers'],
                'sequence_production_method' => $sequenceAttr['sequence_production_method'],
                'sequence_perform_form' => $sequenceAttr['sequence_perform_form'],
                'sequence_duration' => $sequenceAttr['sequence_duration'],
                'sequence_level' => $sequenceAttr['sequence_level'],
                'sequence_asset' => $sequenceAttr['sequence_asset'],
                'sequence_cloth_solution' => $firstStoryboard['cloth_solution'], // 布料
                'sequence_storyboard' => $storyboardIdStr,
                'sequence_type' => 'motion_capture', // 序列类型 动作捕捉
                'sequence_status' => $statusNotStart['id'],
            ]
        ], $sequenceData);

        $entityModel = model(EntityModel::class);
        $entityModel->startTrans();
        try {
            $sequence = $entityModel->addItem($sequenceData);
            if ($sequence === false) {
                if ($entityModel->getErrorCode() == \think\exception\ErrorCode::DATA_ALREADY_EXISTS) {
                    throw new LogicException("Create Sequence Failure:" . $entityModel->getError(), ErrorCode::MOTION_SEQUENCE_ALREADY_EXIST);
                }
                throw new LogicException("Create Sequence Failure:" . $entityModel->getError(), ErrorCode::ENTITY_ADD_FAILURE);
            }
//            $entityModel->execute("UPDATE entity SET json = JSON_SET(IFNULL(json,'{}'),'$.storyboard_motion_sequence',{$sequence['id']},'$.storyboard_sequence','{$sequenceData['name']}') where id in ($storyboardIdStr)");
            $entityModel->execute("UPDATE entity SET json = JSON_SET(IFNULL(json,'{}'),'$.storyboard_motion_sequence',{$sequence['id']},'$.storyboard_sequence','') where id in ($storyboardIdStr)"); // 组装动捕序列和组装序列互相替换
            // 移除之前的镜头
            if (!empty($sequenceList)) {
                $sequence['sequence_changed'] = CommonService::instance('entity')->commonBatchUpdate($sequenceList, module_by_code('sequence'), false);
            }
            // 通过发布新建的序列实体新建动捕任务
            EntityService::getInstance()->publish(['id' => $sequence['id']], 'sequence', $tenantId, $statusNotStart['code'], $taskReadyStatus['code']);
            $entityModel->commit();
        } catch (\Throwable $e) {
            $entityModel->rollback();
            throw $e;
        }

        return $sequence;
    }

    /**
     * 动捕序列列表
     * @param $param
     * @return array
     * @throws \Exception
     */
    public function motionSequenceList($param)
    {
        $fields = $param['fields'] ?? "sequence.id,sequence.name,sequence.initial_episode_code,sequence.showings_number";
        $order = $param['order'] ?? "sequence.initial_episode_code ASC,sequence.showings_number ASC,sequence.name ASC";
        $entityModel = model(EntityModel::class);
        $page = $param['page'];

        //加入额外条件 是否发布版本
        $isPublish = $param['is_publish'] ?? null;
        $data = [];
        if (isset($isPublish)) {
            $entityModel = $entityModel->alias("sequence");
            $publishStatus = $isPublish == "yes";
            if ($publishStatus) {
                $param['filter']['version.id'] = ["EXP", "IS NOT NULL"];
            } else {
                $param['filter']['version.id'] = ["GT", 0];
            }
            $taskModuleId = module_by_code("task")['id'];
            $param['filter']["JSON_UNQUOTE(JSON_EXTRACT(sequence.json, '$.sequence_type'))"] = "motion_capture";
            $entityList = $entityModel
                ->join("task on task.entity_id = sequence.id", "LEFT")
                ->join("version on version.link_id = task.id and version.module_id = {$taskModuleId}", "LEFT")
                ->where($param['filter'])
                ->field(join(',', build_complex_query_fields(explode(",", $fields), '__')))
                ->order($order)
                ->group("sequence.id")
                ->page(...$page)
                ->select();

            foreach ($entityList as $entityIndex => $entityItem) {
                $entityList[$entityIndex] = separate_complex_query_data($entityItem, '__');
            }

            $data['rows'] = $entityList;
        } else {
            $param['filter']['sequence.sequence_type'] = 'motion_capture';
            $entityModel->setCurrentModuleCode('sequence');

            $data['total'] = $entityModel->alias('sequence')
                ->where(parse_filter_to_tp_where($param['filter'] ?? [], 'sequence'))
                ->count();

            $data['rows'] = $entityModel->alias('sequence')
                ->where(parse_filter_to_tp_where($param['filter'] ?? [], 'sequence'))
                ->field(join(',', build_complex_query_fields(explode(",", $fields), '__')))
                ->order($order)
                ->page(...$page)
                ->select();
            foreach ($data['rows'] as &$row) {
                $row = separate_complex_query_data($row, '__');
            }
        }

        if (empty($data['rows'])) {
            return $data;
        }
        // 查询每个序列中的实体任务
        $motionTaskStepCode = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_task_step_code');
        if (empty($motionTaskStepCode)) {
            throw new LogicException('missing motion track step code config', ErrorCode::MISSING_CONFIG);
        }
        $sequenceIds = array_column(array_column($data['rows'], 'sequence'), 'id');
        $motionTaskLatestVersionMap = [];
        if (empty($sequenceIds)) {
            return $data;
        }
        $motionTask = model(TaskModel::class)->where(['entity_id' => ['IN', $sequenceIds], 'step_type' => 'step', 'code' => $motionTaskStepCode])->select();

        $motionTaskMap = [];
        // 查询每个任务中的版本
        if (!empty($motionTask)) {
            $motionTaskIds = [];
            foreach ($motionTask as $motionTaskItem) {
                $motionTaskIds[] = $motionTaskItem['id'];
                $motionTaskMap[$motionTaskItem['entity_id']] = $motionTaskItem;
            }

            $versionParam = [
                'filter' => [
                    'module_id' => module_by_code('task')['id'],
                    'link_id' => ['IN', $motionTaskIds],
                    'is_deprecate' => 'no',
                ],
                'fields' => 'max(id) as id',
                'group' => 'link_id'
            ];
            $motionTaskLatestVersions = VersionService::getInstance()->getManyLatestVersion($versionParam);
            $motionTaskLatestVersionMap = array_column($motionTaskLatestVersions, null, 'link_id');
        }
        $data['rows'] = array_map(function ($item) use ($motionTaskMap, $motionTaskLatestVersionMap) {
            $item['motion_task'] = null;
            $item['motion_task_latest_version'] = null;
            if (!empty($motionTaskMap[$item['sequence']['id']])) {
                $item['motion_task'] = $motionTaskMap[$item['sequence']['id']];
                $item['motion_task']['entity_code'] = $item['sequence']['name'];
                if (!empty($motionTaskLatestVersionMap[$motionTaskMap[$item['sequence']['id']]['id']])) {
                    $item['motion_task_latest_version'] = $motionTaskLatestVersionMap[$motionTaskMap[$item['sequence']['id']]['id']];
                }
            }
            return $item;
        }, $data['rows']);
        return $data;
    }

    /**
     * 动捕序列详情
     * @param $param
     * @return array|mixed
     * @throws \Exception
     */
    public function motionSequence($param)
    {
        $param['filter']['sequence.sequence_type'] = 'motion_capture';
        $param['fields'] = explode(',', $param['fields']);
        // 序列导演查询
        if (!in_array('sequence.sequence_motion_director', $param['fields'])) {
            $param['fields'][] = 'sequence.sequence_motion_director';
        }
        // 序列动捕师查询
        if (!in_array('sequence.sequence_motion_captor', $param['fields'])) {
            $param['fields'][] = 'sequence.sequence_motion_captor';
        }
        // 序列动捕演员查询
        if (!in_array('sequence.sequence_motion_actor', $param['fields'])) {
            $param['fields'][] = 'sequence.sequence_motion_actor';
        }
        $param['fields'] = implode(',', $param['fields']);
        $commonService = CommonService::instance('entity');
        $data = $commonService->find($param, 'sequence');
        if (empty($data)) {
            throw new LogicException("sequence not found", ErrorCode::ENTITY_NOT_FOUND);
        }

        if (!empty($data['sequence_storyboard'])) {
            multiple_sort_by($data['sequence_storyboard'], ['initial_episode_code' => 'asc', 'showings_number' => 'desc', 'name' => 'asc']);
        }

        $motionTaskStepCode = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_task_step_code');
        if (empty($motionTaskStepCode)) {
            throw new LogicException('missing motion track step code config', ErrorCode::MISSING_CONFIG);
        }
        // 查询序列的第一个任务为动捕任务
        $data['motion_task'] = model(TaskModel::class)->where(['entity_id' => $data['sequence']['id'], 'step_type' => 'step', 'code' => $motionTaskStepCode])->find();
        $data['motion_task_latest_version'] = null;
        if (!empty($data['motion_task'])) {
            $data['motion_task_latest_version'] = VersionService::getInstance()->getLatestVersion($data['motion_task']['id']);
        }

        $data['tmp_sequence_motion_captor'] = explode(',', $data['sequence']['sequence_motion_captor']);
        $data['tmp_sequence_motion_actor'] = explode(',', $data['sequence']['sequence_motion_actor']);
        $userIdList = [$data['sequence']['sequence_motion_director'], ...$data['tmp_sequence_motion_captor'], ...$data['tmp_sequence_motion_actor']];
        $userIdList = array_filter($userIdList, function ($userId) {
            return intval($userId) > 0;
        });

        // 查询用户信息
        if (!empty($userIdList)) {
            $userFields = 'id,name,avatar,union_id';
            $userList = model(UserModel::class)->where(['id' => ['IN', implode(',', $userIdList)]])->field($userFields)->select();
            $userList = array_column($userList, null, 'id');
            $data['sequence_motion_director_user'] = $userList[$data['sequence']['sequence_motion_director']] ?? null;
            foreach ($data['tmp_sequence_motion_captor'] as $userId) {
                if (!isset($userList[$userId])) {
                    continue;
                }
                $data['sequence_motion_captor_user'][] = $userList[$userId];
            }
            foreach ($data['tmp_sequence_motion_actor'] as $userId) {
                if (!isset($userList[$userId])) {
                    continue;
                }
                $data['sequence_motion_actor_user'][] = $userList[$userId];
            }
        }
        unset($data['tmp_sequence_motion_actor'], $data['tmp_sequence_motion_captor']);

        return $data;

    }

    /**
     * 通过实体查找对应的动捕序列任务
     * @param $entityId
     * @return array
     * @throws Exception
     */
    public function findMotionSequenceTask($entityId)
    {
        $motionSequenceRes = [];
        $entityData = model(EntityModel::class)->where(['id' => $entityId])->find();
        $entityData['json'] = json_decode($entityData['json'], true);
        if (empty($entityData['json']['storyboard_motion_sequence'])) {
            return [];
        }
        // 查找动捕序列实体
        $motionSequenceData = model(EntityModel::class)
            ->field('id,name,initial_episode_code,showings_number')
            ->where(['id' => $entityData['json']['storyboard_motion_sequence']])
            ->find();
        if (empty($motionSequenceData)) {
            return [];
        }
        // 查找相关任务
        $motionTaskStepCode = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_task_step_code');
        if (empty($motionTaskStepCode)) {
            throw new LogicException('missing motion track step code config', ErrorCode::MISSING_CONFIG);
        }
        $taskData = model(TaskModel::class)
            ->field("id,name,entity_id,json_unquote(json_extract(json,'$.task_status')) as task_status")
            ->where(['entity_id' => $motionSequenceData['id'], 'step_type' => 'step', 'code' => $motionTaskStepCode])
            ->find();
        $StatusData = model(StatusModel::class)
            ->field('id,name,code')
            ->where(['id' => $taskData['task_status']])
            ->find();
        // 查找版本
        $versionData = VersionService::getInstance()->getTaskMaxVersionInfo([$taskData['id']]);
        $versionTaskList = array_column($versionData, null, 'link_id');

        $motionSequenceRes['sequence'] = $motionSequenceData;
        $motionSequenceRes['version'] = $versionTaskList[$taskData['id']]['number'] ?? null;
        $motionSequenceRes['sequence_status'] = $StatusData;
        return $motionSequenceRes;
    }

    /**
     * 获取动捕序列分镜清单
     * @param $param
     * @return array
     * @throws \think\Exception
     */
    public function getMotionSequenceStoryboardList($param)
    {
        $filter = $param['filter'];
        $sequence = model(EntityModel::class)
            ->where([
                'id' => $filter['id'],
                "JSON_UNQUOTE(JSON_EXTRACT(`json`, '$.sequence_type'))" => "motion_capture",
                "module_id" => module_by_code('sequence')['id']
            ])
            ->field("id,JSON_UNQUOTE(JSON_EXTRACT(`json`, '$.sequence_storyboard')) as sequence_storyboard")->find();


        if (empty($sequence) || empty($sequence["sequence_storyboard"])) {
            return [];
        }

        $sequenceStoryboard = explode(",", $sequence["sequence_storyboard"]);

        $storyboardList = model(EntityModel::class)
            ->where([
                "id" => ["IN", $sequenceStoryboard]
            ])
            ->field("id,name,showings_number,initial_episode_code,JSON_UNQUOTE(JSON_EXTRACT(`json`, '$.storyboard_media')) as storyboard_media")
            ->select();

        if (empty($storyboardList)) {
            return [];
        }

        $mediaIds = array_column($storyboardList, "storyboard_media");
        $mediaIds = array_unique(explode(",", join(",", $mediaIds)));

        $mediaMap = [];
        if (!empty($mediaIds)) {
            $mediaList = model(MediaModel::class)->where(["id" => ["IN", $mediaIds]])->field("id,param")->select();
            foreach ($mediaList as $mediaListItem) {
                $mediaListItem['param'] = json_decode($mediaListItem['param'], true);
                $mediaMap[$mediaListItem['id']] = $mediaListItem;
            }
        }

        return array_map(function ($storyboardItem) use ($mediaMap) {
            if (!empty($storyboardItem['storyboard_media'])) {
                $storyboardMedia = explode(",", $storyboardItem['storyboard_media']);
                $currentStoryboardMedia = [];
                foreach ($storyboardMedia as $storyboardMediaItem) {
                    if (isset($mediaMap[$storyboardMediaItem])) {
                        $currentStoryboardMedia[] = $mediaMap[$storyboardMediaItem];
                    }
                }
                $storyboardItem["media"] = $currentStoryboardMedia;
            }
            return $storyboardItem;
        }, $storyboardList);
    }

    /**
     * 组装动捕序列
     * @param array $storyboardId
     * @param array $sequenceData
     * @param int $tenantId
     * @param int $userId
     * @return array|bool
     * @throws \Exception
     */
    public function saveMotionSequence($storyboardId, array $sequenceData, int $tenantId, int $userId)
    {
        $lockKey = '';
        if (!empty($sequenceData['id'])) {
            $lockData = model(EntityModel::class)->find($sequenceData['id']);
            if (empty($lockData)) {
                throw new LogicException('Sequence not found', ErrorCode::ENTITY_NOT_FOUND);
            }
            $lockKey = sprintf("%d:%s:%s:%s", $lockData['project_id'], $lockData['initial_episode_code'], $lockData['showings_number'], $lockData['name']);
        } else {
            $lockKey = sprintf("%d:%s:%s:%s", $sequenceData['project_id'], $sequenceData['initial_episode_code'], $sequenceData['showings_number'], $sequenceData['name']);
        }
        $mutex = new PHPRedisMutex([Redis::connection()->client()], 'motion_sequence_update:' . $lockKey, 10);
        return $mutex->synchronized(function () use ($storyboardId, $sequenceData, $tenantId, $userId) {
            /**
             * 1. 查询出要组装的分镜
             * 2. 合并动捕数据
             * 3. 添加动捕
             * 4. 更新分镜和动捕的关系
             */
            //判定是保存,还是新增
            $addStoryboard = [];
            $deleteStoryboard = [];
            $sequenceDataJson = [];
            if (!empty($sequenceData['id'])) {
                $sequenceEntity = model(EntityModel::class)->find($sequenceData['id']);
                $sequenceDataJson = json_decode($sequenceEntity['json'], true);
                //判定是否有更改,没有改动直接结束
                if (!empty($sequenceEntity)) {
                    if (isset($storyboardId)) {

                        $oldSequenceStoryboard = [];
                        if (!empty($sequenceDataJson['sequence_storyboard'])) {
                            $oldSequenceStoryboard = explode(",", $sequenceDataJson['sequence_storyboard']);
                            $oldSequenceStoryboard = array_filter($oldSequenceStoryboard, function ($oldSequenceStoryboardItem) {
                                return !empty($oldSequenceStoryboardItem);
                            });
                        }

                        $newSequenceStoryboard = $storyboardId;

                        if (!empty(array_diff($oldSequenceStoryboard, $newSequenceStoryboard)) || !empty(array_diff($newSequenceStoryboard, $oldSequenceStoryboard))) {
                            //比较出新增的,与删除的
                            $addStoryboard = array_diff($newSequenceStoryboard, $oldSequenceStoryboard);
                            $deleteStoryboard = array_diff($oldSequenceStoryboard, $newSequenceStoryboard);
                        }
                    }
                }
            } else {
                // 获取集数映射字典
                $episodeData = model(EntityModel::class)->field('id,name,code,module_id')
                    ->where([
                        'project_id' => $sequenceData['project_id'],
                        'module_id' => module_by_code('episode')['id'],
                        'code' => $sequenceData['initial_episode_code']
                    ])
                    ->find();
                if (empty($episodeData)) {
                    throw new LogicException("missing Episode", ErrorCode::MISSING_EPISODE);
                }
                $sequenceData['initial_episode_code'] = $episodeData['code'];
                $sequenceData['entity_id'] = $episodeData['id'];
                $sequenceData['entity_module_id'] = $episodeData['module_id'];
            }

            $sequenceAttr = [];
            $storyboardIdStr = "";
            $sequenceList = [];
            if (!empty($storyboardId)) {
                $fields = [
                    "id",
                    "name",
                    "code",
                    "tenant_id",
                    "project_id",
                    "duration",
                    "description",
                    "initial_episode_code",
                    "showings_number",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_sequence')) AS sequence",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_text_script')) AS text_script",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_cloth_solution')) AS cloth_solution",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_fx')) AS fx",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_illumination')) AS illumination",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS asset",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_level')) AS level",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_style')) AS style",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_type')) AS type",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_species')) AS perform_species",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_numbers')) AS perform_numbers",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_perform_form')) AS perform_form",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_production_method')) AS production_method",
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_motion_sequence')) AS motion_sequence"
                ];

                $storyboardList = model(EntityModel::class)->where(['id' => ['IN', $storyboardId]])->field($fields)->select();
                $sequenceAttr = $this->mergeSequenceAttrByStoryboards($storyboardList);
                $sequenceStoryboardMap = [];
                $needRemoveStoryboardSequenceList = [];


                $storyboardKList = [];
                foreach ($storyboardList as $storyboard) {
                    // 过滤掉  $addStoryboard
                    if ($storyboard['production_method'] == "手K") {
                        $storyboardKList[] = $storyboard['id'];
                    }

                    if (intval($storyboard['motion_sequence']) > 0) {
                        if ((!empty($sequenceData['id']) && $storyboard['motion_sequence'] != $sequenceData['id']) || empty($sequenceData['id'])) {
                            $needRemoveStoryboardSequenceList[$storyboard['motion_sequence']] = $storyboard['motion_sequence'];
                            $sequenceStoryboardMap[$storyboard['motion_sequence']][] = $storyboard['id'];
                        }
                    }
                }

                //取其交集
                if (!empty($sequenceData['id'])) {
                    //取其交集
                    $addStoryboard = array_intersect($addStoryboard, $storyboardKList);
                } else {
                    $addStoryboard = $storyboardKList;
                }
                //需要移除分镜的序列
                if (!empty($needRemoveStoryboardSequenceList)) {
                    $sequenceList = model(EntityModel::class)->where(['id' => ['IN', implode(',', $needRemoveStoryboardSequenceList)]])->field("id,json->>'$.sequence_storyboard' AS sequence_storyboard")->select();
                    foreach ($sequenceList as $sequenceIndex => $sequenceItem) {
                        $sequenceItem['sequence_storyboard'] = explode(',', $sequenceItem['sequence_storyboard']);
                        $sequenceItem['sequence_storyboard'] = array_diff($sequenceItem['sequence_storyboard'], $sequenceStoryboardMap[$sequenceItem['id']] ?? []);
                        $sequenceItem['sequence_storyboard'] = implode(',', $sequenceItem['sequence_storyboard']);
                        $sequenceList[$sequenceIndex] = $sequenceItem;
                    }
                }
                $storyboardIdStr = implode(',', array_column($storyboardList, 'id'));
            }
            $statusNotStart = StatusService::getInstance()->getOne(['code' => 'not_started'], 'id,code');
            $taskReadyStatus = StatusService::getInstance()->getTaskDefaultReadyStatus();
            if (!empty($sequenceData['id'])) {

                if (!empty($storyboardId)) {
                    // 序列json
                    $sequenceDataJson = array_merge($sequenceDataJson, [
                        'sequence_style' => $sequenceAttr['sequence_style'],
                        'sequence_perform_species' => $sequenceAttr['sequence_perform_species'],
                        'sequence_perform_numbers' => $sequenceAttr['sequence_perform_numbers'],
                        'sequence_production_method' => $sequenceAttr['sequence_production_method'],
                        'sequence_perform_form' => $sequenceAttr['sequence_perform_form'],
                        'sequence_duration' => $sequenceAttr['sequence_duration'],
                        'sequence_storyboard' => $storyboardIdStr,
                        'sequence_type' => 'motion_capture', // 序列类型 动作捕捉
                        'sequence_status' => $statusNotStart['id'],
                    ]);
                    $sequenceData['json'] = $sequenceDataJson;
                } elseif (isset($storyboardId)) {
                    $sequenceData['json'] = [
                        'sequence_production_method' => '捕捉',
                        'sequence_type' => 'motion_capture', // 序列类型 动作捕捉
                    ];
                }
            } else {
                $stepWorkflowCode = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_sequence_step_workflow_code');
                if (empty($stepWorkflowCode)) {
                    throw new LogicException('missing config key task_config[motion_sequence_step_workflow_code]', ErrorCode::MISSING_CONFIG);
                }
                $stepWorkflow = model(StepWorkflowModel::class)->where(['code' => $stepWorkflowCode])->field('id')->find();
                if (empty($stepWorkflow)) {
                    throw new LogicException("missing step workflow $stepWorkflowCode", ErrorCode::STEP_WORKFLOW_CONFIG_NOT_FOUND);
                }
                // 序列数据

                $newSequenceData = [
                    'code' => generate_code($sequenceData['name'], ""),
                    'tenant_id' => $tenantId,
                    'module_id' => module_by_code('sequence')['id'],
                    'step_workflow_id' => $stepWorkflow['id']
                ];

                if (!empty($storyboardId)) {
                    $newSequenceData['json'] = [
                        'sequence_style' => $sequenceAttr['sequence_style'],
                        'sequence_perform_species' => $sequenceAttr['sequence_perform_species'],
                        'sequence_perform_numbers' => $sequenceAttr['sequence_perform_numbers'],
                        'sequence_production_method' => $sequenceAttr['sequence_production_method'],
                        'sequence_perform_form' => $sequenceAttr['sequence_perform_form'],
                        'sequence_duration' => $sequenceAttr['sequence_duration'],
                        'sequence_cloth_solution' => $storyboardList[0]['cloth_solution'], // 布料
                        'sequence_storyboard' => $storyboardIdStr,
                        'sequence_type' => 'motion_capture', // 序列类型 动作捕捉
                        'sequence_status' => $statusNotStart['id'],
                    ];
                } else {
                    //默认数据
                    $newSequenceData['json'] = [
                        'sequence_production_method' => '捕捉',
                        'sequence_type' => 'motion_capture', // 序列类型 动作捕捉
                    ];
                }
                $sequenceData = array_merge($newSequenceData, $sequenceData);
            }

            $entityModel = model(EntityModel::class);
            $publishStoryboard = array_merge($addStoryboard, $deleteStoryboard);
            $entityModel->startTrans();
            try {
                if (!empty($sequenceData['id'])) {
                    if (count($sequenceData) > 1) {
                        $sequence = $entityModel->modifyItem($sequenceData);
                        if ($sequence === false) {
                            $sequence = $sequenceData;
                        }
                    } else {
                        $sequence = $sequenceData;
                    }
                } else {
                    $sequence = $entityModel->addItem($sequenceData);
                    if ($sequence === false) {
                        if ($entityModel->getErrorCode() == \think\exception\ErrorCode::DATA_ALREADY_EXISTS) {
                            throw new LogicException("Create Sequence Failure:" . $entityModel->getError(), ErrorCode::MOTION_SEQUENCE_ALREADY_EXIST);
                        }
                        throw new LogicException("Create Sequence Failure:" . $entityModel->getError(), ErrorCode::ENTITY_ADD_FAILURE);
                    }
                }

                //关联上序列 , 关联上了序列 ,需要统一的变为动捕镜头
                if (!empty($storyboardIdStr)) {
                    $entityModel->execute("UPDATE entity SET json = JSON_SET(IFNULL(json,'{}'),'$.storyboard_motion_sequence',{$sequence['id']},'$.storyboard_sequence','') where id in ($storyboardIdStr)"); // 组装动捕序列和组装序列互相替换
                }

                //将手K变成捕捉
                if (!empty($addStoryboard)) {
                    $addStoryboardStr = join(",", $addStoryboard);
                    $entityModel->execute("UPDATE entity SET json = JSON_SET(IFNULL(json,'{}'),'$.storyboard_production_method','捕捉') where id in ($addStoryboardStr)"); // 组装动捕序列和组装序列互相替换
                }

                //清空序列
                if (!empty($deleteStoryboard)) {
                    $deleteStoryboardStr = join(",", $deleteStoryboard);
                    $entityModel->execute("UPDATE entity SET json = JSON_SET(IFNULL(json,'{}'),'$.storyboard_motion_sequence','','$.storyboard_production_method','手K') where id in ($deleteStoryboardStr)"); // 组装动捕序列和组装序列互相替换
                }

                // 移除之前的镜头
                if (!empty($sequenceList)) {
                    $sequence['sequence_changed'] = CommonService::instance('entity')->commonBatchUpdate($sequenceList, module_by_code('sequence'), false);
                }
                // 通过发布新建的序列实体新建动捕任务
                if (empty($sequenceData['id']) || (isset($storyboardId) && empty($storyboardId))) {
                    EntityService::getInstance()->publish(['id' => $sequence['id']], 'sequence', $tenantId, $statusNotStart['code'], $taskReadyStatus['code']);
                }
                $entityModel->commit();

                if (!empty($publishStoryboard)) {
                    Client::send('entity_publish', build_queue_data_with_xu(['filter' => ['id' => ["IN", $publishStoryboard]], 'module_code' => "storyboard", 'tenant_id' => $tenantId, 'user_id' => $userId])); // 间隔三十秒进行发布
                }
            } catch (\Throwable $e) {
                $entityModel->rollback();
                throw $e;
            }

            return $sequence;
        });

    }


    /**
     * 移除动捕分镜
     * @param $storyboardIds
     * @param $tenantId
     * @param $userId
     * @return array|int
     * @throws \Exception
     */
    public function revokeMotionSequenceStoryboard($storyboardIds, $tenantId, $userId)
    {
        $sequenceId = model(EntityModel::class)->where(["id" => ["IN", $storyboardIds]])
            ->field("JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_motion_sequence')) AS motion_sequence")
            ->value('motion_sequence');
        if (empty($sequenceId)) {
            throw new LogicException('sequence not found', ErrorCode::ENTITY_NOT_FOUND);
        }
        $sequence = model(EntityModel::class)->find($sequenceId);
        if (empty($sequence)) {
            throw new LogicException('sequence not found', ErrorCode::ENTITY_NOT_FOUND);
        }
        $lockKey = sprintf("%d:%s:%s:%s", $sequence['project_id'], $sequence['initial_episode_code'], $sequence['showings_number'], $sequence['name']);
        $mutex = new PHPRedisMutex([Redis::connection()->client()], 'motion_sequence_update:' . $lockKey, 5);
        return $mutex->synchronized(function () use ($storyboardIds, $tenantId, $userId) {
            // 将关系置空
            $storyboardList = model(EntityModel::class)->where(["id" => ["IN", $storyboardIds]])
                ->field("id,JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_motion_sequence')) AS motion_sequence")
                ->select();
            $motionSequenceIds = [];
            foreach ($storyboardList as $storyboardItem) {
                if (!empty($storyboardItem['motion_sequence'])) {
                    $motionSequenceIds[$storyboardItem['motion_sequence']] = $storyboardItem['motion_sequence'];
                }
            }
            if (empty($motionSequenceIds)) {
                return [];
            }
            $motionSequenceList = model(EntityModel::class)
                ->where(['id' => ["IN", $motionSequenceIds]])
                ->field("id,json->>'$.sequence_storyboard' AS sequence_storyboard")
                ->select();

            foreach ($motionSequenceList as $motionSequenceIndex => $motionSequenceItem) {
                $motionSequenceItem['sequence_storyboard'] = explode(',', $motionSequenceItem['sequence_storyboard']);
                $motionSequenceItem['sequence_storyboard'] = array_diff($motionSequenceItem['sequence_storyboard'], $storyboardIds);
                $motionSequenceItem['sequence_storyboard'] = implode(',', $motionSequenceItem['sequence_storyboard']);
                $motionSequenceList[$motionSequenceIndex] = $motionSequenceItem;
            }

            $storyboardIdStr = join(",", $storyboardIds);
            $entityModel = model(EntityModel::class);
            $entityModel->startTrans();
            try {
                $result = $entityModel->execute("UPDATE entity SET json = JSON_SET(IFNULL(json,'{}'),'$.storyboard_motion_sequence','','$.storyboard_production_method','手K') where id in ($storyboardIdStr)");
                CommonService::instance('entity')->commonBatchUpdate($motionSequenceList, module_by_code('sequence'), false);
                $entityModel->commit();
                // 由于属性更改 需要重新发布
                Client::send('entity_publish', build_queue_data_with_xu(['filter' => ['id' => ["IN", $storyboardIds]], 'module_code' => "storyboard", 'tenant_id' => $tenantId, 'user_id' => $userId])); // 间隔三十秒进行发布
                return $result;
            } catch (\Throwable $e) {
                $entityModel->rollback();
                throw $e;
            }
        });
    }

    /**
     * 获取序列下的分镜资产
     * @param $param
     * @return array|false|mixed|string
     */
    public function findSequenceStoryboardAsset($param)
    {

        $filter = $param['filter'];
        $page = $param['page'] ?? [1, C("default_page_total")];
        $sequenceId = $filter['sequence_id'];
        $sequence = model(EntityModel::class)
            ->field("id,project_id,JSON_UNQUOTE(JSON_EXTRACT(`json`, '$.sequence_storyboard')) as sequence_storyboard")
            ->find($sequenceId);

        if (empty($sequence)) {
            return [];
        }

        // 是否全局查询
        $isGlobal = $filter['is_global'] ?? "no";
        unset($filter['is_global'], $filter['sequence_id']);

        $entityFilter = $filter;
        $closedStatus = StatusService::getInstance()->getTaskDefaultClosedStatus();
        if ($isGlobal == "yes") {

            $entityFilter = array_merge([
                'project_id' => $sequence['project_id'],
                'module_id' => module_by_code("asset")['id'],
            ], $entityFilter);

        } else {
            $sequenceStoryboard = $sequence["sequence_storyboard"] ?? "";
            $sequenceStoryboard = explode(",", $sequenceStoryboard);
            $entityList = model(EntityModel::class)
                ->where(["id" => ["IN", $sequenceStoryboard]])
                ->field([
                    "JSON_UNQUOTE(JSON_EXTRACT(json, '$.storyboard_asset')) AS asset",
                ])->select();

            $entityIds = [];
            foreach ($entityList as $entityItem) {
                if (!empty($entityItem['asset'])) {
                    $entityIds = array_merge($entityIds, explode(",", $entityItem['asset']));
                }
            }
            $entityIds = array_unique($entityIds);
            if (empty($entityIds)) {
                return [];
            }

            $entityFilter["id"] = ["IN", $entityIds];
//            if (isset($filter['asset_category'])) {
//                $entityFilter["JSON_UNQUOTE(JSON_EXTRACT(entity.json, '$.asset_category'))"] = $filter['asset_category'];
//            }
        }

        // 取消的不被查出来
        $entityFilter["JSON_UNQUOTE(JSON_EXTRACT(json, '$.asset_status'))"] = ["NEQ", $closedStatus['id']];

        $fields = [
            "id", "name", "code", "initial_episode_code", "showings_number",
            "JSON_UNQUOTE(JSON_EXTRACT(entity.json, '$.asset_category')) AS asset_category",
            "JSON_UNQUOTE(JSON_EXTRACT(entity.json, '$.asset_category')) AS asset_grade",
            "JSON_UNQUOTE(JSON_EXTRACT(entity.json, '$.asset_perform_species')) AS asset_perform_species"
        ];

        return model(EntityModel::class)
            ->where($entityFilter)
            ->field($fields)
            ->page(...$page)
            ->select();
    }

    /**
     * 获通过实体查找对应的动捕序列任务列表
     * @param $entityId
     * @return array[]
     * @throws \think\Exception
     */
    public function getMotionSequenceTaskList($entityId)
    {
        $result = $this->findMotionSequenceTask($entityId);
        return [$result];
    }
}
